#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "cache.h"

#define FIFO 0
#define LRU 1
#define CUSTOMIZADO 2

/**
 ============================================================================
  Implementações de métodos para acesso ao chunk de vídeo
 ============================================================================
 */

/*
 * Decrementa a posição de todos os elementos da ListaChunk a partir de uma posição inicial
 */
void decrementarPosicao(ListaChunk *L, int posicaoPilhaInicio) {

	PChunk aux = L->Primeiro->prox;
	while (aux != NULL)
	{
		if(aux->itemChunk.auxValor >= posicaoPilhaInicio) {
			aux->itemChunk.auxValor = aux->itemChunk.auxValor - 1;
		}
		aux = aux->prox;
	}
}

/**
 * Obtém um PChunk (ponteiro para chunk) com base no idVideo e no índice do chunk dentro do vídeo
 */
PChunk get(ListaChunk L, int idVideo, int indiceChunk) {

	PChunk aux = L.Primeiro->prox;
	while (aux != NULL)
	{
		if(aux->itemChunk.idVideo == idVideo && aux->itemChunk.indiceChunk == indiceChunk) {
			return aux;
		}
		aux = aux->prox;
	}
	return (NULL);
}


/**
 * Verifica se a lista de chunks está vazia
 * L -> estrutura ListaChunk
 */
int isListaChunkVazia(ListaChunk L) {
	return (L.Primeiro->prox == NULL);
}

/**
 * Aloca uma lista encadeada de chunks
 * L -> estrutura ListaChunk
 */
void criarListaChunk(ListaChunk *L) {

	L->Primeiro = (PChunk)malloc(sizeof(Chunk));
	if(L->Primeiro == NULL) {
		printf("out of memory");
	}
	L->Ultimo = (PChunk)malloc(sizeof(Chunk));
	L->Primeiro->ant = NULL;
	L->Primeiro->prox = NULL;
	L->Ultimo->ant = NULL;
	L->Ultimo->prox = NULL;
	L->size = 0;
}

/**
 * Insere a referência para um item na lista encadeada
 * L -> lista encadeada
 * P -> ponteiro para o item a ser inserido
 */
void inserirPonteiroChunk(ListaChunk *L, PChunk P) {
	if(isListaChunkVazia(*L)) {
		L->Primeiro->prox = P;
		P->prox = NULL;
		P->ant = NULL;
	} else	{
		P->ant = L->Ultimo->prox->prox;
		L->Ultimo->prox->prox = P;
		P->prox = NULL;
	}
	L->Ultimo->prox = P;
	L->size = L->size + 1;
}

/**
 * Insere um chunk
 * na lista encadeada de Chunks
 */
void inserirChunk(ListaChunk *L, int idVideo, int indiceChunk, int tamanhoChunk, int posicaoPilha) {

	PChunk P = (PChunk)malloc(sizeof(Chunk));
	ItemChunk item;
	item.idVideo = idVideo;
	item.indiceChunk = indiceChunk;
	item.tamanhoChunk = tamanhoChunk;
	item.auxValor = posicaoPilha;

	P->itemChunk = item;

	inserirPonteiroChunk(L,P);
}

/**
 * Imprime os dados da lista encadeada de chunks
 */
void imprimirListaChunk(ListaChunk L) {

	PChunk aux = L.Primeiro->prox;
	while (aux != NULL)
	{
		printf("\nId Video: %d, Indice chunk: %d Tamanho: %d, p: %d ", aux->itemChunk.idVideo, aux->itemChunk.indiceChunk, aux->itemChunk.tamanhoChunk, aux->itemChunk.auxValor);
		aux = aux->prox;
	}
}

/**
 * Libera a memória alocada pela lista encadeada de chunks
 */
ListaChunk *liberaListaChunk(ListaChunk L) {
	PChunk p = L.Primeiro->prox;
	while (p != NULL) {
		PChunk t = p->prox;
		free(p);
		p = t;
	}
	free(L.Primeiro);
	free(L.Ultimo);
	return (NULL);
}



/**
 ============================================================================
  Implementações de métodos para acesso ao cache
 ============================================================================
 */

/**
 * Aloca uma lista encadeada de ItemCache
 * C -> estrutura Cache
 */
void criarCache(Cache *C, int tamanho) {

	C->Primeiro = (PItemCache)malloc(sizeof(ItemCache));
	C->Ultimo = (PItemCache)malloc(sizeof(ItemCache));
	C->Primeiro->prox = NULL;
	C->Ultimo->prox = NULL;
	C->size = tamanho;
	C->ocup = 0;
	C->tempo = 0;
	C->numElementos = 0;
}

/**
 * Verifica se o cache está vazio
 */
int isCacheVazio(Cache C) {
	return (C.Primeiro->prox == NULL);
}

/**
 * Verifica se o cache tem o espaço disponível informado no parâmetro
 */
int temEspacoCache(Cache C, int tamanho) {
	return (C.ocup + tamanho) < C.size;
}

/*
 * Remove o próximo elemento apontado por itemCache do Cache C
 */
void exclui(Cache *C, PItemCache itemCache) {
	PItemCache morta;
	morta = itemCache->prox;
	itemCache->prox = morta->prox;
	C->ocup = C->ocup - morta->itemChunk.tamanhoChunk;
	C->numElementos = C->numElementos - 1;
	free (morta);
}

/**
 * Remover item do cache segundo uma das políticas
 * (0) FIFO, (1) LRU ,(2) CUSTOMIZADO
 */
void removerItem(Cache *C, int politica) {
	if(politica == FIFO) {
	   exclui(C,C->Primeiro);
	} else if (politica == LRU) {
		PItemCache itemCache = getLRU(*C);
		exclui(C, itemCache);
	} else if (politica == CUSTOMIZADO) {
		PItemCache itemCache = C->Primeiro;
		exclui(C, itemCache);
	} else {
		printf("A politica '%d' é inválida", politica);
		exit(1);
	}
}

/**
 * Insere a referência para um item na lista encadeada Cache
 * C -> lista encadeada de ItemCache
 * P -> ponteiro para o ItemCache a ser inserido
 *
 * Retorna 1 caso o ponteiro tenha sido inserido corrretamente
 * Retorna 0 caso o cache já esteja cheio e o ponteiro não é inserido
 */
int inserirPonteiroItemCache(Cache *C, PItemCache P) {

	if(temEspacoCache(*C,P->itemChunk.tamanhoChunk) <= 0) {
		return 0;
	}

	if(isCacheVazio(*C))
		C->Primeiro->prox = P;
	else
	{
		C->Ultimo->prox->prox = P;
	}
	C->Ultimo->prox = P;
	P->prox = NULL;
	C->ocup = C->ocup + P->itemChunk.tamanhoChunk;
	C->tempo = C->tempo + 1;
	C->numElementos = C->numElementos + 1;

	return 1;
}

/**
 * Insere um ItemChunk no Cache
 *
 * Retorna 1 caso o chunk tenha sido inserido corrretamente
 * Retorna 0 caso o cache já esteja cheio e o chunk não é inserido
 */
int inserirItemCache(Cache *C, PChunk chunk) {

	if(temEspacoCache(*C,chunk->itemChunk.tamanhoChunk) <= 0) {
		return 0;
	}

	PItemCache P = (PItemCache)malloc(sizeof(ItemCache));

	P->itemChunk = chunk->itemChunk;
	P->tempoAcesso = C->tempo;
	P->posicao = C->numElementos;
	P->prox = NULL;
	//P->ant = NULL;

	return inserirPonteiroItemCache(C, P);

}
/**
 * Obtém um ItemCache caso o PChunk já esteja no cache, do contrário o retorno é (NULL)
 */
PItemCache getNoCache(Cache *C, PChunk chunk) {

	PItemCache aux = C->Primeiro->prox;
	while (aux != NULL)
	{
		if(aux->itemChunk.idVideo == chunk->itemChunk.idVideo && aux->itemChunk.indiceChunk == chunk->itemChunk.indiceChunk) {
			aux->tempoAcesso = C->tempo;
			C->tempo = C->tempo + 1;
			return aux;
		}
		aux = aux->prox;
	}
	return (NULL);
}

/*
 * Obtém o PItemCache que foi acessado a mais tempo que todos os outros do cache
 */
PItemCache getLRU(Cache C) {
	PItemCache aux = C.Primeiro->prox;
	PItemCache retorno = C.Primeiro;
	while (aux != NULL)
	{
		if(aux->prox != NULL) {
			if( aux->prox->tempoAcesso < aux->tempoAcesso) {
				retorno = aux;
			}
		}
		aux = aux->prox;
	}

	return retorno;
}

/*
 * Retorna o ItemCache com o maior número de acessos
 */
PItemCache getDistante(Cache C) {

	PItemCache aux = C.Primeiro->prox;
	PItemCache retorno = C.Primeiro;
	while (aux != NULL)	{
		printf("\nPosicao: %d, Ultimo acesso: %d", aux->posicao,aux->tempoAcesso);
		printf("\nId Video: %d, Indice chunk: %d Tamanho: %d, p: %d \n", aux->itemChunk.idVideo, aux->itemChunk.indiceChunk, aux->itemChunk.tamanhoChunk, aux->itemChunk.auxValor);
		aux = aux->prox;
	}

	return retorno;
}

/**
 * Imprime os dados que estão no cache
 */
void imprimirCache(Cache C) {
	PItemCache aux = C.Primeiro->prox;
	while (aux != NULL)	{
		printf("\nPosicao: %d, Ultimo acesso: %d", aux->posicao,aux->tempoAcesso);
		printf("\nId Video: %d, Indice chunk: %d Tamanho: %d, p: %d \n", aux->itemChunk.idVideo, aux->itemChunk.indiceChunk, aux->itemChunk.tamanhoChunk, aux->itemChunk.auxValor);
		aux = aux->prox;
	}
}

Cache *liberaCache(Cache C) {
	PItemCache p = C.Primeiro->prox;
	while (p != NULL) {
		PItemCache t = p->prox;
		free(p);
		p = t;
	}
	free(C.Ultimo);
	free(C.Primeiro);
	return (NULL);
}

